/**
 * 
 */
package org.wattdepot.cli.command;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Calculates the max or min energy consumption for a given time or day.
 * 
 * @author George Lee
 *
 */
public class EnergyStatsCommand extends WattDepotCliCommand {

  /**
   * Creates a new instance of the energy stats command.
   * 
   * @param client The client instance that will retrieve our data.
   */
  public EnergyStatsCommand(WattDepotClient client) {
    super(client);
  }

  /**
   * Find the max/min/average energy generated between the given dates.
   * 
   * Sample invocations:
   * energystats generated SIM_OAHU_GRID from 2009-11-01 to 2009-11-30 hourly
   * energystats generated SIM_OAHU_GRID from 2009-11-01 to 2009-11-30 daily
   * @see org.wattdepot.cli.command.WattDepotCliCommand#doCommand(java.lang.String)
   * 
   * @param command The command passed from the command line interface.
   * @return The average/max/min energy generated or an error message if one occurs.
   */
  @Override
  public String doCommand(String command) {
    StringTokenizer tokenizer = new StringTokenizer(command);
    List<String> tokens = new ArrayList<String>();
    while (tokenizer.hasMoreTokens()) {
      tokens.add(tokenizer.nextToken());
    }
    
    if (!tokens.get(0).equals("energystats")) {
      return WattDepotCliCommand.reportUnknownCommand(command);
    }
    
    if (tokens.size() != 8) {
      return this.reportError(PARAMETER_COUNT_ERROR);
    }
    
    if (!tokens.get(1).equals("generated")) {
      return this.reportError(PARAMETER_ERROR + tokens.get(1));
    }
    
    String source = tokens.get(2);
    
    if (!tokens.get(3).equals("from")) {
      return this.reportError(PARAMETER_ERROR + tokens.get(3));
    }
    
    //Convert timestamps
    XMLGregorianCalendar start, end;
    try {
      start = Tstamp.makeTimestamp(tokens.get(4));
    }
    catch (Exception e) {
      return this.reportError(TSTAMP_ERROR + tokens.get(4));
    }
    
    if (!tokens.get(5).equals("to")) {
      return this.reportError(PARAMETER_ERROR + tokens.get(5));
    }
    
    try {
      end = Tstamp.makeTimestamp(tokens.get(6));
    }
    catch (Exception e) {
      return this.reportError(TSTAMP_ERROR + tokens.get(6));
    }
    
    // Average should be the same either way, so use the quicker one.
    if (tokens.get(7).equals("daily")) {
      return this.getDailyStats(source, start, end);
    }
    
    else if (!tokens.get(7).equals("hourly")) {
      return this.reportError(PARAMETER_ERROR + tokens.get(7));
    }
    
    return this.getHourlyStats(source, start, end);
  }

  /**
   * Finds the energy consumed for a given period of time.
   * Information is taken daily.
   * 
   * @param source The source to poll for data.
   * @param start The start of the time interval.
   * @param end The end of the time interval.
   * @return The average, max, and min energy generated or an error message if one occurs.
   */
  private String getDailyStats(String source, XMLGregorianCalendar start,
      XMLGregorianCalendar end) {
    
    XMLGregorianCalendar startTime = start;
    XMLGregorianCalendar endTime = Tstamp.incrementDays(start, 1);
    XMLGregorianCalendar maxDay = start;
    XMLGregorianCalendar minDay = start;
    double energy;
    double max = Double.MIN_VALUE;
    double min = Double.MAX_VALUE;
    double total = 0;
    int count = 0;
    
    try {
      while (Tstamp.lessThan(endTime, end)) {
        count++;
        energy = client.getEnergyGenerated(source, startTime, endTime, 60);
        
        //Convert to megawatt hours
        energy /= 1000000;
        
        if (energy > max) {
          max = energy;
          maxDay = endTime;
        }
        if (energy < min) {
          min = energy;
          minDay = endTime;
        }
        
        total += energy;
        
        startTime = endTime;
        endTime = Tstamp.incrementDays(endTime, 1);
      }
      
      double average = total / count;
      
      StringBuffer buf = new StringBuffer();
      buf.append(String.format("Max: %.1f MWh at %s\n", max, maxDay.toString()));
      buf.append(String.format("Min: %.1f MWh at %s\n", min, minDay.toString()));
      buf.append(String.format("Average: %.1f MWh\n", average));
      
      return buf.toString();
    }
    catch (ResourceNotFoundException e) {
      return this.reportError(SOURCE_ERROR + source);
    }
    catch (NotAuthorizedException e) {
      return this.reportError(CONNECTION_ERROR + source);
    }
    catch (BadXmlException e) {
      return this.reportError(BAD_XML_ERROR + source);
    }
    catch (MiscClientException e) {
      return this.reportError(CONNECTION_ERROR + source);
    }
  }

  /**
   * Finds the energy consumed for a given source during a period of time.
   * The information is taken hourly.
   * 
   * @param source The source to poll for data.
   * @param start The start of the time interval.
   * @param end The end of the time interval.
   * @return The average, max, and min energy generated or an error message if one occurs.
   */
  private String getHourlyStats(String source, XMLGregorianCalendar start,
      XMLGregorianCalendar end) {
    XMLGregorianCalendar startTime = start;
    XMLGregorianCalendar endTime = Tstamp.incrementHours(start, 1);
    XMLGregorianCalendar maxDay = start;
    XMLGregorianCalendar minDay = start;
    double energy;
    double max = Double.MIN_VALUE;
    double min = Double.MAX_VALUE;
    double total = 0;
    int count = 0;
    
    try {
      while (Tstamp.lessThan(endTime, end)) {
        count++;
        energy = client.getEnergyGenerated(source, startTime, endTime, 60);
        
        //Convert to megawatt hours
        energy /= 1000000;
        
        if (energy > max) {
          max = energy;
          maxDay = endTime;
        }
        if (energy < min) {
          min = energy;
          minDay = endTime;
        }
        
        total += energy;
        
        startTime = endTime;
        endTime = Tstamp.incrementHours(endTime, 1);
      }
      
      double average = total / count;
      
      StringBuffer buf = new StringBuffer();
      buf.append(String.format("Max: %.1f MWh at %s\n", max, maxDay.toString()));
      buf.append(String.format("Min: %.1f MWh at %s\n", min, minDay.toString()));
      buf.append(String.format("Average: %.1f MWh\n", average));
      
      return buf.toString();
    }
    catch (ResourceNotFoundException e) {
      return this.reportError(SOURCE_ERROR + source);
    }
    catch (NotAuthorizedException e) {
      return this.reportError(CONNECTION_ERROR);
    }
    catch (BadXmlException e) {
      return this.reportError(BAD_XML_ERROR);
    }
    catch (MiscClientException e) {
      return this.reportError(CONNECTION_ERROR);
    }
  }

  /**
   * Returns information used to invoke the energy stats command.
   * @see org.wattdepot.cli.command.WattDepotCliCommand#getHelp()
   * @return Instructions on invoking the energystats command.
   */
  @Override
  public String getHelp() {
    return "energystats generated {source} from {start} to {end} {hourly|daily}\n" +
      "  Find the max/min/average energy consumption by a source between the given dates.";
  }

}
